using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using Rotempco.Core.ORM.Logic;
using Rotempco.Core.CSV.Objects;
using Rotempco.Core.CSV.Objects.Exceptions;

namespace Rotempco.Core.CSV.Logic
{
    public class CSVMappingLogic<T> where T : class, new()
    {
        public static CSVMappingResult<T> MapCSV(byte[] byteArray, CSVMapping<T> csvMapping)
        {
            List<string> lines = ByteArrayToStrings(byteArray, csvMapping.EncodingType);
            return MapCSV(lines, csvMapping);
        }

        public static CSVMappingResult<T> MapCSV(List<string> lines, CSVMapping<T> csvMapping)
        {
            CSVMappingResult<T> csvMappingResult = new CSVMappingResult<T>();

            Type tType = typeof(T);

            foreach (string line in lines.Skip(csvMapping.SkipLines))
            {
                if (string.IsNullOrWhiteSpace(line))
                    continue;
                try
                {
                    List<string> parts = line.Split(csvMapping.Delimiter).ToList();
                    T t = new T();
                    bool parsedSuccessfully = true;

                    foreach (CSVParameterMapping csvParameterMapping in csvMapping.CSVParameterMappingContainer.CSVParameterMappingList)
                        parsedSuccessfully &= GetFilledObject(lines, csvMappingResult, tType, line, parts, t, csvParameterMapping, csvMapping);

                    if (parsedSuccessfully)
                        csvMappingResult.ResultList.Add(t);
                }
                catch (CSVMappingParameterParsingException)
                {
                    return csvMappingResult;
                }
                catch (Exception exception)
                {
                    csvMappingResult.ErrorLines.Add(new CSVMappingErrorLine()
                    {
                        ErrorDescription = exception.Message,
                        Line = line,
                        LineElement = null,
                        LineNumber = lines.IndexOf(line),
                        PropertyName = null
                    });

                    if (csvMapping.StopOnError)
                        return csvMappingResult;
                }
            }

            return csvMappingResult;
        }

        private static bool GetFilledObject(List<string> lines, CSVMappingResult<T> csvMappingResult, Type tType, string line, List<string> parts, T t, CSVParameterMapping csvParameterMapping, CSVMapping<T> csvMapping)
        {
            string part = "";

            try
            {
                part = parts[csvParameterMapping.CSVColumnIndex];

                PropertyInfo propertyInfo = tType.GetProperty(csvParameterMapping.PropertyName);
                if (propertyInfo == null)
                    throw new Exception("Property '" + csvParameterMapping.PropertyName + "' was not found on object type: " + tType.FullName);
                else
                {
                    CSVParameterMappingCallback<T> callback = null;
                    if (csvParameterMapping is CSVParameterMappingCallback<T>)
                        callback = csvParameterMapping as CSVParameterMappingCallback<T>;

                    object value = null;
                    bool success = false;

                    if (csvMapping.RunOnAllCSVEntries != null)
                        part = csvMapping.RunOnAllCSVEntries(part);

                    if (callback != null && callback.ParseCSVString != null)
                        part = callback.ParseCSVString(part);

                    if (DBNullHelper.TryToType(part, csvParameterMapping.Type, out value))
                    {
                        propertyInfo.SetValue(t, value, null);
                        success = true;
                    }

                    if (callback != null && callback.SetParameter != null)
                        callback.SetParameter(t, part);

                    if (!success && !csvMapping.SoftMapping)
                        throw new Exception("Failed to parse property '" + csvParameterMapping.PropertyName + " with value: " + part);
                }
            }
            catch (Exception exception)
            {
                csvMappingResult.ErrorLines.Add(new CSVMappingErrorLine()
                {
                    ErrorDescription = exception.Message,
                    Line = line,
                    LineElement = part,
                    LineNumber = lines.IndexOf(line),
                    PropertyName = csvParameterMapping.PropertyName
                });

                if (csvMapping.StopOnError)
                    throw new CSVMappingParameterParsingException();

                return false;
            }

            return true;
        }



        private static List<string> ByteArrayToStrings(byte[] byteArray, Encoding encoding)
        {
            List<string> lines = new List<string>();
            using (StreamReader streamReader = new StreamReader(new MemoryStream(byteArray), encoding))
            {
                lines = streamReader.ReadToEnd().Split(Environment.NewLine.ToArray(), StringSplitOptions.RemoveEmptyEntries).ToList();
            }
            return lines;
        }

    }
}
